chore: sync standards components - #3771
Merged
Merged
Conversation
kyle-sexton
pushed a commit
that referenced
this pull request
Sep 5, 2026
…e sibling bumps Main's fleet-wide prompt audit (#3770) rewrote descriptions and changelog text across the plugins this branch also touches, and #3771/#3773 moved their versions past the numbers taken here. Every conflict was in a version line, a changelog heading, or prose the audit rewrote. The manifests take main's descriptions whole, since this branch changed only their version fields, and each version is renumbered strictly above main's: claude-ops 0.42.10, mutation-testing 0.3.16, verification 0.6.5. The changelogs keep both sides in order, with this branch's entry under its new number. The testing plugin's change is dropped rather than merged. Its one edit added a presence gate and a code-metrics fallback to the marketplace-skill section in `write/context/organize.md`, and the audit deleted that whole section as naming plugins no installed marketplace carries. There is nothing left to gate, so testing returns to main's content, version, and changelog exactly. The cross-plugin pointer this branch adds now lives only in `verification:measure` and `mutation-testing:principles`. The `verification:measure` metrics bullet takes both sides: main's worktree-safe `git show` sequence stays as the manual fallback, under this branch's `/code-metrics:audit-<measure>` pointer. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DVwWe4VVyx57dQ1AL7m35p
kyle-sexton
pushed a commit
that referenced
this pull request
Sep 7, 2026
…acement (#3871) Closes #3509 ## Summary `pr-body-linkage-gate.sh` timed out on **every** recorded run in the measurement window — 423 timeouts against a 15 s ceiling, the worst blocked count in the #3508 campaign. A gate that is killed before it renders a verdict protects nothing, so its cost is part of its contract. Its MCP-surface sibling `pr-linkage-mcp-gate.sh` and the validator both share is fixed here too. **The parent's stated cause does not hold, and this PR does not act on it.** #3508 attributes the cost to per-field `jq` forks needing a new shared helper in `lib/hook-utils.sh`. Shard #3520 (PR #3779) established the real mechanism, and merged PR #3788 fixed 34 hook scripts across 17 plugins while touching `lib/hook-utils.sh` **zero times**. This PR follows that precedent: **`lib/hook-utils.sh` and every `plugins/*/hooks/hook-utils.sh` copy are untouched** (unmerged PRs #3740 and #3838 own that file). The fix is entirely in-file. The mechanism, re-verified on this host before any edit was made: | Form | clone-family | `execve` | | --- | --- | --- | | `V=$(jq . f)` | 1 | 1 | | `V=$(jq . f 2>/dev/null)` | **2** | 1 | | `{ V=$(jq . f); } 2>/dev/null` | **1** | 1 | | `V=$(printf \| jq \| tr)` | 4 | 2 | | `V=$(cat -- f)` | 1 | 1 | | `V=$(<f)` | **0** | **0** | | `V=$(shellfunc)` | 1 | 0 | | out-variable call | **0** | 0 | | `read < <(printf …)` | 1 | 0 | Bash elides the extra fork and execs in the command substitution's own subshell only when the command carries no redirection of its own. A `2>/dev/null`, a `<<<`, or a pipeline inside the substitution defeats that. ## Fix Four shapes, all in-file: 1. **Per-field `jq` batched into one process.** Both gates read payload fields through `printf '%s' "$INPUT" | jq -r … 2>/dev/null | tr -d '\r'`, once per field — 4 clones and 2 execs each, **five times over** on the MCP surface, all asking about one buffered string. One `hook::jq_fields` call (the library's existing batched reader, called not changed) answers every field, and CR-strips exactly as the `tr` did. 2. **Redirection hoisted onto the enclosing group.** `ORIGIN=$(git … 2>/dev/null || true)` became `{ ORIGIN=$(git …) || ORIGIN=""; } 2>/dev/null`. The group holds exactly one command, so nothing beyond that git call is silenced. 3. **The shared validator's helpers write into a caller-named variable.** `strip_html_comments`, `mask_markdown_code`, `section_content` and `trim` were each read through `$(…)` over a `< <(printf …)` line reader: **12 forks and zero extra `execve` per judged body** — pure process-creation latency. They now use `printf -v` and an in-shell line split. `linkage::chomp_to` reproduces the trailing-newline strip that command substitution performed, which is the one thing a naive out-variable conversion gets wrong. 4. **`$(<file)` for the `--body-file` read, `printf -v '%q'` for wrapper re-quoting, `hook::json_str_object_to` for the telemetry envelope** — replacing a `cat`, a `printf` substitution, and a `jq -n` that bash can do itself. The `$(dirname …)` source line was already fixed in both gates by #3771. `pr-body-linkage-gate.sh` and `pr-linkage-validator.sh` now invoke **no external command of their own at all**; every remaining spawn on their path belongs to `lib/hook-utils.sh`. `pr-linkage-mcp-gate.sh` keeps three, each justified: the origin-remote scope guard, the `jq -e` defer-guard on a repo's own `settings.json` (behind a `[[ -f ]]` probe), and a carriage-return fallback described below. ### Where the MCP reader's behaviour could change, and what it does about each `hook::jq_fields` CR-strips every value it returns; the validator only strips a CR at end of line. A body with a **mid-line** CR would therefore be judged against different text — and stripping is the **permissive** direction: `## Sum<CR>mary` becomes a section that was previously missing, turning a BLOCK into a silent ALLOW. The batch therefore also reports whether the raw body holds a CR at all, and the MCP body is re-read losslessly with its own `jq` only in that case, which no real payload hits. Review of the first revision found two field shapes where that batched reader itself flipped a per-field DENY to ALLOW; both are fixed in the second commit (`fe3fd235`), and the differential below was re-run over them: - **The CR probe was not type-safe.** `contains("\r")` errors on a non-string body, one erroring filter fails the whole batch, and a failed batch exited 0 — so a body of `5`, `true`, `{"a":1}` or `["x"]`, which the per-field reader rendered as text and blocked, was allowed. The probe now goes through `tostring` first, so it is total over every JSON type. A batch that **still** fails (a `tool_input` or payload root that is not an object) falls back to the per-field reads it replaced instead of allowing outright, so a batch failure is now exactly as fail-closed as the per-field reader was: a determinable bad body still blocks. That fallback is unreachable for any object `tool_input`, so the fenced spawn counts do not move. - **Trailing newlines survived on the exact-match fields.** `$( )` chomped them from every per-field read, so `"owner": "acme-corp\n"` or a tool name with a trailing newline matched the guards and was gated; `hook::jq_fields` keeps the newline and both slipped past. `TOOL`, `HOOK_CWD`, `T_OWNER`, `T_REPO` and `BODY` are now chomped in-shell, byte-identical to their `$(jq -r …)` form whenever they carry no CR. **One accepted stricter change remains.** A CR *inside* `tool_name`, `owner` or `repo` used to stay in the value, so it never matched and the call was allowed; `hook::jq_fields` strips it, the value matches, and the body is judged. Neither GitHub nor the MCP server produces such a value; the stricter direction is kept and named in the CHANGELOG rather than asserted away. ## Verification **Behaviour, proven by differential rather than argued.** - **End-to-end, 154 payloads (85 Bash, 69 MCP), exit code, stdout and stderr compared byte-for-byte** between the merge-base gates and these, after `fe3fd235`. Bash surface: every body-flag spelling (`--body`, `-b`, `--body=`, `--body-file`, `-F`, attached forms), stdin and substitution heredocs, multiple-heredoc and unterminated cases, missing body files, `--repo`/`-R` and `cd` escapes, `env -S` / `env` / `sudo` wrappers, `gh.exe` and `./gh`, CRLF and mid-line-CR bodies, NUL bytes, NBSP/BOM/ZWSP/U+2028/U+2029, `--fill`/`--web`, absent and non-string `command`, trailing-newline and CR `cwd`. MCP surface: the tool x owner x body matrix, absent/empty/null body, non-string bodies (`5`, `true`, `{"a":1}`, `["x"]`, `false`), absent and non-object `tool_input`, NUL and CR in `owner`, trailing newline and CR in `owner`/`repo`/`tool_name`/`cwd`. **Bash gate: 85/85 verdict-identical. MCP gate: 67/69 verdict-identical; the remaining 2 are the CR-in-`owner` and CR-in-`tool_name` cases above, ALLOW to DENY.** Before `fe3fd235` the same run showed 7 DENY-to-ALLOW mismatches (the four non-string bodies, `owner\n`, `repo\n`, `tool_name\n`); all 7 are gone and no new one appeared. Bash's own "ignored null byte" warning line, whose text carries the script path, is excluded from the byte comparison. Denies: 69 at the merge base, 71 here. - **Validator differential, 425 bodies**, hand cases plus a seeded fuzz corpus over heading/fence/comment/CR/NBSP/backtick-run tokens. **0 mismatches.** - **Trailing-newline invariance** proved separately, because `hook::jq_fields` preserves trailing newlines where `$(printf | jq)` stripped them: 24 validator cases and 9 hook cases, 0 mismatches; the MCP gate's own fields are now chomped as well. - Contract suites: `pr-body-linkage-gate.test.sh` **146/146**, `pr-linkage-mcp-gate.test.sh` **37/37** (nine new cases: the four non-string bodies block, `owner\n` / `repo\n` / `tool_name\n` still gate, CR-in-owner gates as the accepted stricter case, a string `tool_input` takes the fallback and allows). **Cost, measured with `strace -f -e trace=clone,clone3,fork,vfork,execve`** — not an xtrace command count, which reads source positions rather than kernel spawns (#3520 measured xtrace at 2 against 8 real spawns on one script). Telemetry sink off; re-measured after `fe3fd235`, unchanged: | Path | clones before | clones after | `execve` before | `execve` after | | --- | --- | --- | --- | --- | | Bash gate, a `gh` call with no `pr` | 8 | **7** | 3 | **2** | | Bash gate, `gh pr create` with a body (ALLOW) | 28 | **11** | 6 | **3** | | Bash gate, `gh pr create` with a body (BLOCK) | 28 | **11** | 6 | **3** | | MCP gate, unrelated tool | 7 | **7** | 2 | **2** | | MCP gate, create (ALLOW) | 37 | **11** | 9 | **4** | | MCP gate, create (BLOCK) | 37 | **11** | 9 | **4** | Two components: the validator refactor alone removes **12 clones with `execve` unchanged** — that half is pure latency, no work removed. The rest is genuinely duplicated work removed: six `jq` processes re-parsing one buffered payload, plus two `tr` calls deleting a byte class bash rewrites in place. **No wall-clock figure is claimed.** This is a Linux host where a spawn costs ~3-5 ms; the campaign's host measures 0.3-0.9 s and is bimodal at 501 concurrent processes. A timing here would say nothing about there, so the process count is reported as the proxy, per #3508's own correction. **New gate: `hooks/pr-linkage-spawn-budget.test.sh`.** Ceilings are the measured counts with **no headroom**, per `hook-budget.md` rule 2. It refuses to report a pass it has not earned: - a self-check first proves the harness can distinguish `$(cmd 2>/dev/null)` from `{ …; } 2>/dev/null` at the kernel level, and **SKIPs** rather than passing if it cannot (no ptrace, no strace); - three mutants must each raise the count above the ceiling or **the suite fails itself**: a redirect moved back inside a substitution (11 -> 12 clones), one field split back out of the batch (11 -> 14 clones, 4 -> 5 execve), a validator helper re-forking (11 -> 13 clones); - it asserts both gates still exit 2 on a failing body, so a budget of zero spawns cannot pass as a no-op. Pointed at HEAD's hooks the new suite reports **13 failures**; against this branch, 23/23 pass. **Gates run** (re-run after `fe3fd235`). `scripts/affected-tests.sh --run`: 151 shell suites pass, 14 selected suites belong to ecosystems the runner does not execute (reported NOT RUN, not skipped), one pre-existing failure noted below; `scripts/check-changelog-parity.sh` in all four modes (`--check`, `--check-order`, `--check-bump origin/main`, `--check-preserved origin/main`) all rc=0; `shellcheck -x` and `shfmt -d` clean on all five scripts; `markdownlint-cli2` and `editorconfig-checker` clean on the changed files; `check-shell-portability.sh`, `check-purged-em-dashes.sh`, `check-silent-skips.sh`, `check-discriminating-test-skips.sh`, `check-killswitch-hoist.sh`, `check-hook-exec-form.sh`, `check-fixture-git-isolation.sh` all rc=0 on the first revision. Manifest bumped 0.55.58 -> **0.55.60** with the matching CHANGELOG entry (see Related for why not 0.55.59), and the README carries the measured share per `hook-budget.md` rule 1. **Pre-existing failures, not from this branch** (both reproduce on a clean `HEAD` checkout, and this branch touches no file either reads): `plugins/claude-ops/skills/plugins/scripts/cache-content-check.test.sh` (2 cases, its own xtrace-based budget probe) and `plugins/session-flow/scripts/tests/test_save_point.py::test_new_origin_falls_back_to_directory_name`. ### Acceptance criteria: two are not fully met, stated plainly | # | Criterion | Status | | --- | --- | --- | | 1 | No more than 2 external spawns on the common path (own shell + at most one `jq`) | **NOT MET — over by one.** The common path is now the hook's own shell, one `jq -e .`, and one batched `jq`. The batched `jq` is the criterion's allowance; the `jq -e .` is `hook::buffer_stdin`'s payload validation inside `lib/hook-utils.sh`, which this PR is fenced off from. Removing it is that library's change to make, in #3740/#3838. | | 2 | `grep`/`sed`/`cut`/`tr`/`basename`/`dirname` on the hot path replaced with builtins | **MET.** None appears in any of the three scripts; `cat` is gone too. | | 3 | Matcher or early guard exits before any spawn for non-matching invocations | **PARTIALLY MET.** The `if: Bash(*gh *)` filter (pre-existing) removes the hook process entirely for non-`gh` calls, and the two in-hook guards short-circuit before any repo I/O. They cannot run before `hook::buffer_stdin`, whose `jq` is the same library spawn as row 1 — the hook must read stdin before it can know what it is looking at. | | 4 | Existing behavioural tests still pass; the guard still blocks what it blocked before | **MET, with one named exception.** 183 contract cases plus the 154-payload differential: every merge-base DENY is reproduced, and two merge-base ALLOWs (a CR inside `owner` or `tool_name`) are now DENY, the stricter direction, recorded in the CHANGELOG. | | 5 | Under 2 s for a single run on a Windows host | **NOT VERIFIED.** No Windows host available; the process-count proxy above is offered instead, and no timing figure is invented. | ## Related - Parent: #3508 (Windows process-creation tax). Its stated cause — per-field `jq` needing a new shared `hook-utils.sh` helper — is not what this PR acts on; see Summary. - Precedent: #3520 / PR #3779 (established redirection placement as the real mechanism) and merged PR #3788 (34 scripts, 17 plugins, `lib/hook-utils.sh` untouched). - **Version and overlap with #3838.** `main` is at `source-control` 0.55.58. Open, ready PR #3838 (`cursor/shell-script-perf-phase1-bb5b`) bumps this plugin to **0.55.59** with its own `## [0.55.59]` heading, and edits **both gate files this PR touches** (`pr-body-linkage-gate.sh`, `pr-linkage-mcp-gate.sh`, introducing `hook::buffer_stdin_to`) plus the three worktree gates and the synced `hook-utils.sh`. This PR therefore takes **0.55.60**, verified against `main`, #3838, #3774 (0.55.58, no bump) and #3740 (tops at 0.55.54). Whichever of #3838 and this PR lands second needs a **rebase of the two gate files, not just a rebump**; that is the merge lane's call, flagged here so it is not a surprise. Sibling #3510 is in flight against this plugin and takes the next version after this one. - Fenced off: unmerged PRs #3740 and #3838 own `lib/hook-utils.sh`; #3510 owns `worktree-add-containment-gate.sh`, `worktree-add-claim-gate.sh` and `worktree-create-gate.sh`, which share this plugin's `CHANGELOG.md` and manifest. Neither set is touched here. - Prior art acknowledged: #1403 / PR #1385, whose revival bar (four contract-test regressions, two failing open) is what the deny-preserving differential above is aimed at. - Budget authority: `docs/conventions/hook-budget/README.md` (#1809), surfaced by `.claude/rules/hook-budget.md`. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01ViPsHkL3ng9xWt2GjEQJob --- _Generated by [Claude Code](https://claude.ai/code/session_01ViPsHkL3ng9xWt2GjEQJob)_ --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: ksextonmelodic <ksextonmelodic@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Automated exact-file synchronization from
melodic-software/standards@d98e1f53a3376fe1c7415f861b9cfcd34d7b684b(requested refmain).No linked issue — recurring automated distribution, not tied to a tracked change.
Fix
Managed component mappings applied to
melodic-software/claude-code-plugins:.github/actionlint.yaml→.github/actionlint.yaml(mode100644).editorconfig-checker.json→.editorconfig-checker.json(mode100644).gitleaks.toml→.gitleaks.toml(mode100644)lychee.toml→lychee.toml(mode100644)components/managed-files-guard/managed-files-guard.yml→.github/workflows/managed-files-guard.yml(mode100644).markdownlint-cli2.jsonc→.markdownlint-cli2.jsonc(mode100644).node-version→.node-version(mode100644)components/path-detection/machine-path-patterns.sh→plugins/guardrails/lib/path-detection/machine-path-patterns.sh(mode100644).claude/rules/pr-body-contract.md→.claude/rules/pr-body-contract.md(mode100644)components/pyright/pyrightconfig.json→.github/standards/pyright/pyrightconfig.json(mode100644).editorconfig→.editorconfig(mode100644).gitattributes→.gitattributes(mode100644)REVIEW.md→REVIEW.md(mode100644)components/runner-policy/package-lock.json→.github/standards/runner-policy/package-lock.json(mode100644)components/runner-policy/package.json→.github/standards/runner-policy/package.json(mode100644)components/runner-policy/policy.json→.github/standards/runner-policy/policy.json(mode100644)components/runner-policy/policy.schema.json→.github/standards/runner-policy/policy.schema.json(mode100644)components/runner-policy/repository-policy.schema.json→.github/standards/runner-policy/repository-policy.schema.json(mode100644)components/runner-policy/runner-policy.mjs→.github/standards/runner-policy/runner-policy.mjs(mode100755).shellcheckrc→.shellcheckrc(mode100644)_typos.toml→_typos.toml(mode100644)Verification
Review and merge this PR normally. Do not hand-edit these managed files downstream; change their standards source instead.
Related
melodic-software/standards@d98e1f53a3376fe1c7415f861b9cfcd34d7b684b— source commit this sync distributes.